// Title:		StringSplitter
// Description:	Utility that takes a String and returns a String Array of
//				broken out "tokens".  Works better then the String.Split()
//				method.
//
// Copyright:	Copyright (c) 2003, All Rights Reserved.
// Company:		MyFamily.com
// Author:		Bill Yetman
// Version:		1.0

using System;
using System.Collections;
using System.Diagnostics;

namespace Pegasus
{
	/// <summary>
	/// Use this class to split strings into an array of string
	/// objects with each individual word as an array element.  This
	/// class works different then the Split command off the String object,
	/// it properly skips all the white space between the delimeters.
	/// </summary>
	/// <remarks>
	/// This is a utility class straight out of the "Professional Java XML
	/// Programming" book.  It has turned out to be a very useful class
	/// that takes a string and returns an array of String objects with
	/// each individual word as an array element.  This is a useful class
	/// that can be used to break up strings.  This class is ment to be
	/// used like StringTokenizer, but it can return empty "tokens", and
	/// takes only one delimiter which may be either a character or a string.
	/// This is an immutable object.
	/// <para>
	/// For example, the text string "help me [lots of blanks here] fix this", if it
	/// split using a blank character as the delimiter will produce a String Array
	/// with the following elements:  S[0] = "help", S[1] = "me", S[2] = "fix", and
	/// S[3] = "this".  Notice that the extra blanks do not produce empty string
	/// entries between "me" and "fix". 
	/// </para>
	/// </remarks>
	public class StringSplitter
	{
		// Reference to the string that is being split.
		private string m_theString;

		// If a character is used as a delimeter, this data member holds
		// that character.
		private char m_theDelim;

		// nextToken is the token beginning at m_thePos
		private int m_thePos;

		// If a string delimeter is used this data member holds that string.
		private string m_theDelimStr;

		// The length in bytes of the delimeter.
		private int m_theDelimLength;

		/// <summary>
		/// This is the constructor that is called when you want
		/// to initialize a string splitter object with a single
		/// character as the delimiter.
		/// </summary>
		/// <param name="split">The string to split.</param>
		/// <param name="delimiter">The single character that is the delimiter.</param>
		/// <param name="startPos">The position to start splitting at.</param>
		public StringSplitter( string split, char delimiter, int startPos )
		{
			// Forward to the private method that will do the work.
			StringSplitterCharConstructor( split, delimiter, startPos );
		}

		/// <summary>
		/// This is a constructor that takes a string to split and
		/// a delimiter character then forwards the call to the
		/// StringSplitter(String, char, int) constructor with the
		/// position to start at set to zero.
		/// </summary>
		/// <param name="split">The string to split.</param>
		/// <param name="delimiter">The single character that is the delimiter.</param>
		public StringSplitter( string split, char delimiter )
		{
			// Forward to the private method that will do the work.
			StringSplitterCharConstructor( split, delimiter, 0 );
		}

		/// <summary>
		/// This is the constructor that is called when you want
		/// to initialize a string splitter object with a string
		/// as the delimiter.
		/// </summary>
		/// <param name="split">The string to split.</param>
		/// <param name="delimiter">The string that is the delimiter.</param>
		/// <param name="startPos">The position to start splitting at.</param>
		public StringSplitter( string split, string delimiter, int startPos )
		{
			// Forward to the private method that will do the work.
			StringSplitterStringConstructor( split, delimiter, startPos );
		}

		/// <summary>
		/// This is a constructor that takes a string to split and
		/// a delimiter string then forwards the call to the
		/// StringSplitter(String, String, int) constructor with the
		/// position to start at set to zero.
		/// </summary>
		/// <param name="split">The string to split.</param>
		/// <param name="delimiter">The string that is the delimiter.</param>
		public StringSplitter( string split, string delimiter )
		{
			// Forward to the private method that will do the work.
			StringSplitterStringConstructor( split, delimiter, 0 );
		}

		/// <summary>
		/// This is the constructor that is called when you want
		/// to initialize a string splitter object with a single
		/// character as the delimiter.
		/// </summary>
		/// <param name="split">The string to split.</param>
		/// <param name="delimiter">The single character that is the delimiter.</param>
		/// <param name="startPos">The position to start splitting at.</param>
		private void StringSplitterCharConstructor( string split, char delimiter, int startPos )
		{
			m_theString = split;
			m_theDelim = delimiter;
			m_thePos = startPos;
			m_theDelimLength = 1;

			if( m_thePos >= m_theString.Length )
			{
				m_thePos = -1;
			}
		}

		/// <summary>
		/// This is the constructor that is called when you want
		/// to initialize a string splitter object with a string
		/// as the delimiter.
		/// </summary>
		/// <param name="split">The string to split.</param>
		/// <param name="delimiter">The string that is the delimiter.</param>
		/// <param name="startPos">The position to start splitting at.</param>
		private void StringSplitterStringConstructor( string split, string delimiter, int startPos )
		{
			m_theString = split;
			m_theDelimStr = delimiter;
			m_thePos = startPos;
			m_theDelimLength = delimiter.Length;

			if( m_thePos >= m_theString.Length )
			{
				m_thePos = -1;
			}
		}

		/// <summary>
		/// This method will return true if there are more tokens left
		/// in the string.  If there are no more tokens possible,
		/// false is returned.
		/// </summary>
		/// <returns>
		/// A boolean value that indicates if there are any more
		/// tokens left to parse in the string.
		/// </returns>
		public bool HasMoreTokens()
		{
			return ( m_thePos >= 0 );
		}
	 
		/// <summary>
		/// This method is called to return the next token in the string.
		/// If there are no more tokens, null is returned to the caller.
		/// </summary>
		/// <returns>
		/// A String with the next token is returned to the caller.
		/// </returns>
		public string NextToken()
		{
			string ret = null;

			if( m_thePos >= 0 )
			{
				int nextPos;
				if( m_theDelimStr == null )
				{
					nextPos = m_theString.IndexOf( m_theDelim, m_thePos );
				}
				else
				{
					nextPos = m_theString.IndexOf( m_theDelimStr, m_thePos );
				}


				if( nextPos >= 0 )
				{
					int size = nextPos - m_thePos;
					ret = m_theString.Substring( m_thePos, size );
					m_thePos = nextPos + m_theDelimLength;
				}
				else
				{
					ret = m_theString.Substring( m_thePos );
					m_thePos = nextPos;
				}
			}

			return ret;
		}

		/// <summary>
		/// This static method is called to split a string that has
		/// a character delimiter into an array of String objects.
		/// </summary>
		/// <param name="split">The string to split.</param>
		/// <param name="delimiter">The character delimiter that separates the tokens in the string.</param>
		/// <returns>
		/// A String array object is returned to the caller with
		/// each separate token as an element of the array.
		/// </returns>
		public static string [] StringSplit( String split, char delimiter )
		{
			ArrayList list = new ArrayList();
			StringSplitter spliter = new StringSplitter( split, delimiter );

			while( spliter.HasMoreTokens() )
			{
				list.Add( spliter.NextToken() );
			}

			return ArrayListToStringArray( list );
		}

		/// <summary>
		/// This static method is called to split a string that has
		/// a string delimiter into an array of String objects.
		/// </summary>
		/// <param name="split">The string to split.</param>
		/// <param name="delimiter">The String delimiter that separates the tokens in the string.</param>
		/// <returns>
		/// A String array object is returned to the caller with
		/// each separate token as an element of the array.
		/// </returns>
		public static string [] StringSplit( string split, string delimiter )
		{
			ArrayList list = new ArrayList();
			StringSplitter spliter = new StringSplitter( split, delimiter );

			while( spliter.HasMoreTokens() )
			{
				list.Add( spliter.NextToken() );
			}

			return ArrayListToStringArray( list );
		}

		/// <summary>
		/// This is a static utility method that is called to change a Vector
		/// into a string array.
		/// </summary>
		/// <param name="list">The ArrayList that we want to change to a String array.</param>
		/// <returns>
		/// An array of String objects is returned to the caller.
		/// </returns>
		private static string[] ArrayListToStringArray( ArrayList list )
		{
			int elementCount = NonEmptyCount( list );
			String [] strings = new String[ elementCount ];
			IEnumerator iter = list.GetEnumerator();
			int x = 0;

			// Only iterate through the ArrayList if we have a non-zero element count.
			if( elementCount > 0 )
			{
				while( iter.MoveNext() )
				{
					string current = iter.Current as string;

					// We don't move empty array entries into the final string array.
					if( current != null && current.Length > 0 )
					{
						strings[ x ] = current;
						x++;
					}
				}
			}

			return strings;
		}

		/// <summary>
		/// Called to return the number of non-empty ArrayList entries in the array list
		/// passed in.
		/// </summary>
		/// <param name="V">
		/// The array list that we want a non-empty count for.
		/// </param>
		/// <returns>
		/// The number of non-empty strings in the ArrayList.
		/// </returns>
		private static int NonEmptyCount(
			ArrayList V )
		{
			int result = 0;
			IEnumerator pIter = V.GetEnumerator();

			while( pIter.MoveNext() )
			{
				String current = (String)pIter.Current;
				if( current.Length > 0 )
					result++;
			}

			return result;
		}

	}
}
